/*
 * Copyright (c) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

/**
 * Used in [[jmmo.engine.property.BoundProperty]] to obtain the value and
 * subscription to properties from which depends this.
 * For `Binding` objects creation it is recommended to use [[jmmo.engine.property.Binding]] object `apply` methods
 * or [[jmmo.engine.property.BindingSeq]] object `apply` method.
 * @tparam R type of `Binding` value
 * @see [[jmmo.engine.property.BoundPropertySpec]]
 * @author Tomas Shestakov
 */
trait Binding[R] {

  /**
   * @return `Binding` value
   */
  def apply(): R

  /**
   * @return sequence of properties from which `Binding` value depends
   */
  def dependencies: Seq[Property[_]]
}

/**
 * Contains factory methods to create instances derived from [[jmmo.engine.property.Binding]].
 */
object Binding {

  /**
   * Creates `Binding` dependent on 1 `Property`.
   * @param f predicate with 1 argument to calculate `Binding` value
   * @param p1 `Property` 1 from which the `Binding` depends
   * @tparam R type of `Binding` value
   * @tparam A `Property` 1 type
   * @return new `Binding` instance
   */
  def apply[R, A](f: (A) => R, p1: Property[A]) = new Binding1(f, p1)

  /**
   * Creates `Binding` dependent on 2 `Properties`.
   * @param f predicate with 1 argument to calculate `Binding` value
   * @param p1 `Property` 1 from which the `Binding` depends
   * @param p2 `Property` 2 from which the `Binding` depends
   * @tparam R type of `Binding` value
   * @tparam A `Property` 1 type
   * @tparam B `Property` 2 type
   * @return new `Binding` instance
   */
  def apply[R, A,B](f: (A,B) => R, p1: Property[A],p2:Property[B]) = new Binding2(f, p1,p2)

  /**
   * Creates `Binding` dependent on 3 `Properties`.
   * @param f predicate with 1 argument to calculate `Binding` value
   * @param p1 `Property` 1 from which the `Binding` depends
   * @param p2 `Property` 2 from which the `Binding` depends
   * @param p3 `Property` 3 from which the `Binding` depends
   * @tparam R type of `Binding` value
   * @tparam A `Property` 1 type
   * @tparam B `Property` 2 type
   * @tparam C `Property` 3 type
   * @return new `Binding` instance
   */
  def apply[R, A,B,C](f: (A,B,C) => R, p1: Property[A],p2:Property[B],p3:Property[C]) = new Binding3(f, p1,p2,p3)

  /**
   * Creates `Binding` dependent on 4 `Properties`.
   */
  def apply[R, A,B,C,D](f: (A,B,C,D) => R, p1: Property[A],p2:Property[B],p3:Property[C],p4:Property[D]) =
    new Binding4(f, p1,p2,p3,p4)

  /**
   * Creates `Binding` dependent on 5 `Properties`.
   */
  def apply[R, A,B,C,D,E](f: (A,B,C,D,E) => R, p1: Property[A],p2:Property[B],p3:Property[C],p4:Property[D],
    p5:Property[E]) = new Binding5(f, p1,p2,p3,p4,p5)

  /**
   * Creates `Binding` dependent on 6 `Properties`.
   */
  def apply[R, A,B,C,D,E,F](f: (A,B,C,D,E,F) => R, p1: Property[A],p2:Property[B],p3:Property[C],p4:Property[D],
    p5:Property[E],p6:Property[F]) = new Binding6(f, p1,p2,p3,p4,p5,p6)

  /**
   * Creates `Binding` dependent on 7 `Properties`.
   */
  def apply[R, A,B,C,D,E,F,G](f: (A,B,C,D,E,F,G) => R, p1: Property[A],p2:Property[B],p3:Property[C],p4:Property[D],
    p5:Property[E],p6:Property[F],p7:Property[G]) = new Binding7(f, p1,p2,p3,p4,p5,p6,p7)

  /**
   * Creates `Binding` dependent on 8 `Properties`.
   */
  def apply[R, A,B,C,D,E,F,G,H](f: (A,B,C,D,E,F,G,H) => R, p1: Property[A],p2:Property[B],p3:Property[C],p4:Property[D],
    p5:Property[E],p6:Property[F],p7:Property[G],p8:Property[H]) = new Binding8(f, p1,p2,p3,p4,p5,p6,p7,p8)

  /**
   * Creates `Binding` dependent on 9 `Properties`.
   */
  def apply[R, A,B,C,D,E,F,G,H,I](f: (A,B,C,D,E,F,G,H,I) => R, p1: Property[A],p2:Property[B],p3:Property[C],p4:Property[D],
    p5:Property[E],p6:Property[F],p7:Property[G],p8:Property[H],p9:Property[I]) = new Binding9(f, p1,p2,p3,p4,p5,p6,p7,p8,p9)
}
